#!/bin/bash

#
# configures aliases of device $1
#
# This script goes out of its way to arrive at the configuration of ip
# aliases described in the ifcfg-$DEV:* and ifcfg-$DEV-range* files from
# whatever existing configuration it may be given: existing aliases not
# specified in the configuration will be removed, netmasks and broadcast
# addrs will be updated on existing aliases, and new aliases will be setup.
#
#   range specification files:
#
# One can specify ranges of alised ipaddress using ifcfg-$DEV-range* files.
# Specify multiple ranges using multiple files, such as ifcfg-eth0-range0 and
# ifcfg-eth0-range1, etc. In these files, the following configuration variables 
# specify the range:
#
#    IPADDR_START    -- ipaddr to start range at. eg "192.168.30.1"
#    IPADDR_END      -- ipaddr to end range at. eg "192.168.30.254"
#    CLONENUM_START  -- interface clone number to start using for this range. eg "0"
#
# The above example values create the interfaces eth0:0 through eth0:253 using
# ipaddrs 192.168.30.1 through 192.168.30.254, inclusive.
#
# Other configuration variables such as NETMASK and BROADCAST may be specified
# in the range file and will apply to all of the ipaddresses in the range. Range
# files also inherit configuration from the ifcfg-$DEV file just like normal.
#
# Note that IPADDR_START and IPADR_END are required to be in the same class-c
# block. I.e. IPADDR_START=192.168.30.1 and IPADDR_END=192.168.31.255 is
# not valid.
#
#   speed with large sets of interfaces:
#
# Considerable effort was spent making this script fast. It can efficiently 
# handle a thousand ip aliases on one interface.
# 
# With large sets of ipaddresses the NO_ALIASROUTING=yes configuration is
# highly recommended. (This can be specified in ifcfg-$DEV and inherited.) This
# prevents this script from setting up routing details for the virtual
# interfaces, which I don't think is needed, because outgoing traffic can use the
# main interface. However, make your own conclusions on what you need.
# 
# My test setup of four class C address blocks on a P166 took 25 seconds of 
# which 16 seconds of this was spent in the ifcconfig calls. Without the 
# NO_ALIASROUTING=yes config an additional 12 seconds is spent in route calls.
#
#   notes on internals:
#
# This script uses the bash "eval" command to lookup shell variables with names
# which are generated from other shell variables. This allows us to, in effect,
# create hashes using the shell variable namesspace by just including the hash
# key in the name of the variable.
#
# This script originally written by: David Harris <dharris@drh.net> 
#                                    Principal Engineer, DRH Internet
#                                    June 30, 1999
#
#            modified by: Bill Nottingham <notting@redhat.com>

TEXTDOMAIN=initscripts
TEXTDOMAINDIR=/etc/locale

device=$1
if [ "$device" = "" ]; then
	echo $"usage: ifup-aliases <net-device> [<parent-config>]\n"
	exit 1
fi

PARENTCONFIG=${2:-ifcfg-$device}
parent_device=$device

cd /etc/sysconfig/network-scripts  
. ./network-functions

# Grab the current configuration of any running aliases device info is placed
# into variables in the form: rdev_$DEVICE_addr, dev_$DEVICE_mb and
# rdevip_$IPGLOP.  A list of all the devices is created in rdev_LIST.

eval $(LC_ALL= LANG= /sbin/ifconfig | LC_ALL=C sed -n '
# Alias name: store the number in TMP, ignore the following address if the
# device name does not match.  This may leave a hanging ||, so dummy X=x
# comands are used to ignore a possible previous condition.
/^[^[:space:]]*:[0-9A-Za-z_]*/ {
	s|^\([^:]*\):\([0-9A-Za-z_]*\).*$|X=x; TMP="\2"; [ "$device" != "\1" ] \|\| |p
	b
}
# Non-alias device: always ignore the following address
/^[^[:space:]]/ {
	s|^.*$|X=x; true \|\| |p
	b
}
# Address: Preceded by a device name, which was converted into
#	condition ||
# So this triggers only for $device.
# Add a trailing || (which will be swallowed by X=x) to keep the syntax
# correct.
/inet addr:[0-9]*\.[0-9]*\.[0-9]*\.[0-9]* *Bcast:[0-9.]* *Mask:[0-9.]*/ {
	s|^.*inet addr:\(\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)\) *Bcast:\([0-9.]*\) *Mask:\([0-9.]*\).*$|eval "rdev_${TMP}_addr=\1; rdev_${TMP}_mb=\7_\6; rdevip_\2_\3_\4_\5=${TMP}; rdev_LIST=\\"\\$rdev_LIST \\${TMP}\\"; " \|\| |p
	b
}') X=x

if [ -z "$rdev_LIST" ]; then
       no_devices_are_up=yes
fi

##for DEVNUM in $rdev_LIST ; do
##eval "
##echo \"rdev_${DEVNUM}_addr = \$rdev_${DEVNUM}_addr\"
##echo \"rdev_${DEVNUM}_mb = \$rdev_${DEVNUM}_mb\" ";
##done
##echo ""; echo "-----"; echo ""

       #
       # Store configuration of the parent device and network
       #

# read from the /etc/sysconfig/network
eval ` (
       . /etc/sysconfig/network;
       echo network_GATEWAY=$GATEWAY\;;
       echo network_GATEWAYDEV=$GATEWAYDEV\;;
) `

# read defaults from the parent config file
[ -f $PARENTCONFIG ] || {
       net_log $"Missing config file $PARENTCONFIG."
       exit 1
}
eval ` (
       . ./$PARENTCONFIG;
       echo default_NETMASK=$NETMASK\;;
       echo default_BROADCAST=$BROADCAST\;;
       echo default_GATEWAY=$GATEWAY\;;
       echo default_NO_ALIASROUTING=$NO_ALIASROUTING\;;
) `
[ -z "$default_GATEWAY" ] && default_GATEWAY=$network_GATEWAY

function ini_env ()
{
       DEVICE=""
       IPADDR=""
       NETMASK=$default_NETMASK
       BROADCAST=$default_BROADCAST
       GATEWAY=$default_GATEWAY
       NO_ALIASROUTING=$default_NO_ALIASROUTING
       ONPARENT=""
}

function is_default_gateway ()
{
	LC_ALL=C /sbin/route -n \
		| awk '$1 == "0.0.0.0" && $2 == "'"$1"'" { found = 1; }
		       END { exit found == 0; }'
}

       #
       # Read the alias configuration files and enable each aliased
       # device using new_interface()
       #

function new_interface ()
{

       ipa=$IPADDR; ipb=${ipa#*.}; ipc=${ipb#*.};
       IPGLOP="${ipa%%.*}_${ipb%%.*}_${ipc%%.*}_${ipc#*.}";
       DEVNUM=${DEVICE#*:}

       MATCH='^[0-9A-Za-z_]*$'
       if (LC_ALL=C; [[ ! "$DEVNUM" =~ $MATCH ]]); then
	      net_log $"error in $FILE: invalid alias number"
	      return 1
       fi

       eval "
              ipseen=\$ipseen_${IPGLOP}; devseen=\$devseen_${DEVNUM};
              ipseen_${IPGLOP}=$FILE; devseen_${DEVNUM}=$FILE;
       ";

       if [ -n "$ipseen" ]; then
              net_log $"error in $FILE: already seen ipaddr $IPADDR in $ipseen"
	      return 1
       fi

       if [ -n "$devseen" ]; then
              net_log $"error in $FILE: already seen device $parent_device:$DEVNUM in $devseen"
	      return 1
       fi

       if [ -z "$DEVICE" -o -z "$IPADDR" ]; then
              net_log $"error in $FILE: didn't specify device or ipaddr"
	      return 1
       fi

       if [ -z "$NETMASK" ]; then
               eval $(/bin/ipcalc --netmask ${IPADDR})
       fi

       if [ -z "$BROADCAST" -o "$BROADCAST" = "$default_BROADCAST" ]; then
               eval $(/bin/ipcalc --broadcast ${IPADDR} ${NETMASK})
               nma=$NETMASK; nmb=${nma#*.}; nmc=${nmb#*.};
               NMGLOP="${nma%%.*}_${nmb%%.*}_${nmc%%.*}_${nmc#*.}";
               if [ ${nma%%.*} -eq 0 ]; then ipnm_a=0; else ipnm_a=${ipa%%.*}; fi
               if [ ${nmb%%.*} -eq 0 ]; then ipnm_b=0; else ipnm_b=${ipb%%.*}; fi
               if [ ${nmc%%.*} -eq 0 ]; then ipnm_c=0; else ipnm_c=${ipc%%.*}; fi
               if [ ${nmc#*.}  -eq 0 ]; then ipnm_d=0; else ipnm_d=${ipc#*.};  fi
               CACHENAME="${NMGLOP}_${ipnm_a}_${ipnm_b}_${ipnm_c}_${ipnm_d}"
               eval "
                       if [ -z \"\$bcastcache_${CACHENAME}\" ]; then
                               eval $(/bin/ipcalc --broadcast $IPADDR $NETMASK)
                               bcastcache_${CACHENAME}=\$BROADCAST
                       else
                               BROADCAST=\$bcastcache_${CACHENAME}
                       fi
               ";
       fi

##echo "";
##echo "----------------------------"
##echo "device = $DEVICE"
##echo "ipaddr = $IPADDR"
##echo "netmask = $NETMASK"
##echo "broadcast = $BROADCAST"
##echo "devnum = $DEVNUM";
##echo "";

       if [ "$no_devices_are_up" = "yes" ]; then
               setup_this=yes
       else

               setup_this=""

               eval "
                       rdev_addr=\$rdev_${DEVNUM}_addr;
                       rdev_mb=\$rdev_${DEVNUM}_mb;
                       rdev_mark=\$rdev_${DEVNUM}_mark;
                       rdevip=\$rdevip_${IPGLOP};
               ";

               if [ -n "$rdev_addr" ]; then
                       if [ "$rdev_addr" = "${IPADDR}" ]; then
                               newmark=keep
                               if [ "$rdev_mb" != "${NETMASK}_${BROADCAST}" ]; then
                                       setup_this=freshen
                               else
                                       setup_this=no
                               fi
                       else
                               if [ "$rdev_mark" != "remove" ]; then
##echo "removing device $parent_device:${DEVNUM} (devnum conflict)"
                                       /sbin/ifconfig $parent_device:${DEVNUM} down
                                       do_netreport=yes
                               fi
                               newmark=remove
                               setup_this=yes
                       fi
                       if [ -n "$rdev_mark" -a "$rdev_mark" != "$newmark" ]; then
                               net_log $"error in ifcfg-${parent_device}: files"
			       return 1
                       fi
                       eval " rdev_${DEVNUM}_mark=\$newmark ";
               else
                       setup_this=yes
               fi

               if [ -n "$rdevip" -a "$rdevip" != "${DEVNUM}" ]; then
                       eval " mark_remove=\$rdev_${rdevip}_mark ";
                       if [ -n "$mark_remove" -a "$mark_remove" != "remove" ]; then
                               net_log $"error in ifcfg-${parent_device}: files"
			       return 1
                       fi
                       if [ "$mark_remove" != "remove" ]; then
                               eval " rdev_${rdevip}_mark=remove ";
##echo "removing device $parent_device:$rdevip (ipaddr conflict)"
                               /sbin/ifconfig $parent_device:$rdevip down
                               do_netreport=yes
                       fi
               fi

       fi

       if [ "$setup_this" = "freshen" ] ; then
               # we can do the freshen stuff right now
##echo "freshening device $DEVICE"
               /sbin/ifconfig $DEVICE netmask $NETMASK broadcast $BROADCAST;
       fi

       if [ "$setup_this" = "yes" ] ; then

##echo "setting up device $DEVICE"

               /sbin/ifconfig ${DEVICE} ${IPADDR} netmask ${NETMASK} broadcast ${BROADCAST}

               if [ "$NO_ALIASROUTING" != yes ]; then

                       GATEWAYDEV=$network_GATEWAYDEV;

                       if [ -n "${GATEWAY}" -a \
			       \( -z "${GATEWAYDEV}" -o "${GATEWAYDEV}" = "${DEVICE}" \) ]; then
                               # set up default gateway, if it isn't already there
			       if ! is_default_gateway "$GATEWAY"; then
				       route add default gw ${GATEWAY} \
					   ${METRIC:+metric $METRIC} ${DEVICE}
			       fi
                       fi

		       /etc/sysconfig/network-scripts/ifup-routes ${DEVICE} ${NAME}

                       do_netreport=yes
                       ifuplocal_queue="$ifuplocal_queue $DEVICE"

               fi

	fi

}

if [ "$BASH_VERSINFO" ]; then shopt -s nullglob; else allow_null_glob_expansion=foo; fi

for FILE in ifcfg-${parent_device}:* ; do
       is_ignored_file "$FILE" && continue
       ini_env;
       . ./$FILE;
       [ -z "$DEVICE" ] && DEVICE=${FILE##ifcfg-}
       [ "$ONPARENT" != "no" -a "$ONPARENT" != "NO" ] && new_interface;
       unset DEVICE
done

for FILE in ifcfg-${parent_device}-range* ; do
       is_ignored_file "$FILE" && continue
       ini_env;
       . ./$FILE;

       ipaddr_prefix=${IPADDR_START%.*}
       ipaddr_startnum=${IPADDR_START##*.}
       ipaddr_endnum=${IPADDR_END##*.}

       if [ "${IPADDR_START%.*}" != "${IPADDR_END%.*}" ]; then
               net_log $"error in $FILE: IPADDR_START and IPADDR_END don't agree"; continue
       fi

       if [ $ipaddr_startnum -gt $ipaddr_endnum ]; then
               net_log $"error in $FILE: IPADDR_START greater than IPADDR_END"; continue
       fi

       ipaddr_num=$ipaddr_startnum
       ipaddr_clonenum=$CLONENUM_START

       while [ $ipaddr_num -le $ipaddr_endnum ]; do
               IPADDR="$ipaddr_prefix.$ipaddr_num"
               DEVICE="$parent_device:$ipaddr_clonenum"
	       [ "$ONPARENT" != "no" -a "$ONPARENT" != "NO" ] && new_interface;
               ipaddr_num=$(($ipaddr_num+1))
               ipaddr_clonenum=$(($ipaddr_clonenum+1))
       done
       
done

       #
       # Remove any devices that should not be around
       #
	for DEVNUM in $rdev_LIST ; do
	       eval " rdev_mark=\$rdev_${DEVNUM}_mark ";
	       if [ -z "$rdev_mark" ]; then
	##echo "removing device $parent_device:${DEVNUM} (lingering)"
	               /sbin/ifconfig $parent_device:${DEVNUM} down
        	       do_netreport=yes
	       fi
	done
       #
       # Notify of new device creation
       #

if [ -n "$do_netreport" ]; then
       do_netreport
fi

if [ -x /sbin/ifup-local ]; then
       for DEVICE in $ifuplocal_queue ; do
               /sbin/ifup-local ${DEVICE}
       done
fi
